package cn.bluech.lib.http;

import android.content.Context;

import androidx.annotation.NonNull;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

/**
 * Description: 证书工厂
 * Create by Codeant on 2019/3/13.
 */
public class SSLFactory {
    public static final SSLSocketFactory ALL_TRUST = getAllTrustFactory();
    public static final SSLFactory DEFAULT = new Builder().build();

    private SSLSocketFactory factory;
    private X509TrustManager manager;

    private SSLFactory(SSLSocketFactory factory, X509TrustManager manager){
        this.factory = factory;
        this.manager = manager;
    }

    public SSLSocketFactory getFactory() {
        return factory;
    }

    public X509TrustManager getManager() {
        return manager;
    }

    private static SSLSocketFactory getAllTrustFactory(){
        //创建一个不验证证书链的证书信任管理器。
        TrustManager[] allTrustCerts = new TrustManager[]{new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] chain,String authType) {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain,String authType) {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        }};

        try {
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, allTrustCerts, new SecureRandom());
            return sslContext.getSocketFactory();
        }catch (Exception e){
            throw new RuntimeException();
        }
    }

    public static class Builder{

        /**
         * 生成一个默认的证书工厂和证书管理器(后续扩展)
         */
        public SSLFactory build(){
            try {
                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                trustManagerFactory.init((KeyStore) null);
                TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
                if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                    throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
                }
                X509TrustManager trustManager = (X509TrustManager) trustManagers[0];
                SSLContext sslContext = SSLContext.getInstance("SSL");
                sslContext.init(null, new TrustManager[]{trustManager}, null);

                return new SSLFactory(sslContext.getSocketFactory(), trustManager);
            } catch (Exception e) {
                throw new RuntimeException();
            }
        }
    }

    private static class PublicKeyTrustManager implements X509TrustManager {
        private List<String> keys;

        private PublicKeyTrustManager(@NonNull List<String> keys){
            this.keys = keys;
        }

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            if((null==chain)||(chain.length==0))
                throw new CertificateException("Server chain is empty");
            if (!(null != authType && authType.equalsIgnoreCase("RSA")))
                throw new CertificateException("Server AuthType is not RSA");

            // 通用的SSL/TLS检查
            try {
                TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
                tmf.init((KeyStore) null);
                for (TrustManager trustManager : tmf.getTrustManagers()) {
                    ((X509TrustManager) trustManager).checkServerTrusted(chain, authType);
                }
            } catch (Exception e) {
                throw new CertificateException(e);
            }

            // 检查公钥
            RSAPublicKey pubKey = (RSAPublicKey) chain[0].getPublicKey();
            // 大数(超过int范围)运算。(这里应该只是把byte转string，如果是，直接转换效率可能更高)
            String encodedKey = new BigInteger(1 /* positive */, pubKey.getEncoded()).toString(16);
            for(String key:keys){
                if(encodedKey.equalsIgnoreCase(key))
                    return;
            }
            throw new CertificateException("Server public key unknown: "+encodedKey);
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }

    /**
     * 使用公钥创建证书
     */
    public static class PublicKeyBuilder extends Builder{
        private List<String> keys = new ArrayList<>();

        /**
         * 添加服务器的公钥(RSA)
         * @param pubKey 服务器公钥
         */
        public PublicKeyBuilder addKey(String pubKey){
            keys.add(pubKey);
            return this;
        }

        @Override
        public SSLFactory build() {
            try {
                PublicKeyTrustManager trustManager = new PublicKeyTrustManager(keys);
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
                return new SSLFactory(sslContext.getSocketFactory(), trustManager);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    /**
     * 使用KeyStore文件创建证书，需要bks格式的证书，jks可以先转换成bks
     */
    public static class KeyStoreBuilder extends Builder{
        private Context context;
        private CertificateFactory certificateFac;
        private KeyStore keyStore;

        public KeyStoreBuilder(Context context){
            this.context = context.getApplicationContext();
            try {
                certificateFac = CertificateFactory.getInstance("X.509");
                keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                keyStore.load(null, null);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        public KeyStoreBuilder addKeyStoreFromAssets(Context context,int assetId){
            InputStream is = null;
            try {
                is = context.getResources().openRawResource(assetId);
                keyStore.setCertificateEntry(String.valueOf(assetId), certificateFac.generateCertificate(is));
            }catch (Exception e){
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException ignored) {
                    }
                }
            }

            return this;
        }

        @Override
        public SSLFactory build() {
            try {
                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                trustManagerFactory.init(keyStore);

                SSLContext sslContext = SSLContext.getInstance("TLS");
                TrustManager[]trustManagers = trustManagerFactory.getTrustManagers();
                sslContext.init(null, trustManagers, new SecureRandom());
                return new SSLFactory(sslContext.getSocketFactory(), (X509TrustManager) trustManagers[0]);
            }catch (Exception e){
                throw new RuntimeException(e);
            }
        }
    }
}
